/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.daydream.template.sequence;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.pajamas.daydream.template.sequence.context.ArgumentContext;
import org.pajamas.daydream.template.sequence.segment.SegmentDefinition;
import org.pajamas.daydream.template.sequence.segment.SegmentDefinitionParser;
import org.pajamas.protocol.Interpreter;
import org.springframework.util.CollectionUtils;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * @author william
 * @since 2024/4/3
 */
public class Sequence implements Interpreter<String, SequenceContext> {
    private final List<SequenceContext> contexts;
    private final String skeleton;
    private final List<SegmentDefinition> definitions;

    Sequence(@Nonnull SegmentDefinitionParser parser, @Nonnull List<SequenceContext> contexts,
             @Nonnull String pattern) {
        Pair<String, List<SegmentDefinition>> pair = parsePattern(pattern, parser);
        this.skeleton = Objects.requireNonNull(pair.getKey());
        this.definitions = Collections.unmodifiableList(pair.getValue());
        this.contexts = contexts;
    }

    @Nonnull
    private static Pair<String, List<SegmentDefinition>> parsePattern(@Nonnull String pattern,
                                                                      @Nonnull SegmentDefinitionParser parser) {
        final StringBuilder skeletonBuilder = new StringBuilder();
        final List<SegmentDefinition> definitions = Lists.newArrayList();

        final char[] chars = pattern.toCharArray();
        int num = 0, idx = 0, subStrBgnIx = 0;
        String subStr, key, defaultValue;
        while (idx < chars.length) {
            subStrBgnIx = idx;
            if (chars[idx] == '{') {
                // find out attr key in {key}
                while (chars[idx] != '}' && ++idx < chars.length) ;
                if (idx == chars.length) {
                    subStr = pattern.substring(subStrBgnIx, idx);
                } else {
                    subStr = pattern.substring(subStrBgnIx + 1, idx).trim();
                    subStr = StringUtils.isBlank(subStr) ? String.valueOf(num) : subStr;

                    // subStr here is a segment
                    definitions.add(parser.parse(subStr));

                    subStr = "{" + num + "}";
                    idx++;
                    num++;
                }
            } else {
                // find out the literal
                while (chars[idx] != '{' && ++idx < chars.length) ;
                subStr = pattern.substring(subStrBgnIx, idx);
            }
            skeletonBuilder.append(subStr);
        }

        return Pair.of(skeletonBuilder.toString(), definitions);
    }

    private List<SequenceContext> mergeContexts(@Nullable List<SequenceContext> runtimeContexts) {
        List<SequenceContext> mergedContexts = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(runtimeContexts)) {
            mergedContexts.addAll(runtimeContexts);
        }
        if (!CollectionUtils.isEmpty(this.contexts)) {
            mergedContexts.addAll(this.contexts);
        }
        return mergedContexts;
    }

    private Object getValueOrderly(@Nullable String key, @Nullable List<SequenceContext> mergeContexts) {
        if (StringUtils.isBlank(key) || CollectionUtils.isEmpty(mergeContexts)) {
            return null;
        }
        for (SequenceContext context : mergeContexts) {
            Object value = context.getValue(key);
            if (value != null) {
                return value;
            }
        }
        return null;
    }

    @Nonnull
    public String interpretSelf() {
        return this.interpret();
    }

    @Nonnull
    public String interpretWithArgs(@Nullable Object... args) {
        return this.interpret(new ArgumentContext(args));
    }

    @Nonnull
    @Override
    public String interpret(@Nullable SequenceContext... contexts) {
        List<SequenceContext> runtimeContexts = Arrays.stream(MoreObjects.firstNonNull(contexts,
                        new SequenceContext[0]))
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        List<SequenceContext> mergeContexts = this.mergeContexts(runtimeContexts);

        Object[] arguments = this.definitions
                .stream()
                .map(definition -> {
                    Object value = this.getValueOrderly(definition.getKey(), mergeContexts);
                    return definition.toString(value);
                })
                .toArray(String[]::new);
        return MessageFormat.format(this.skeleton, arguments);
    }
}
